#include <buola/gui.h>

#include <buola/image/cfont.h>
#include <buola/gui/ctoolkit.h>
#include <buola/gui/cclipboard.h>
#include <buola/gui/cconfig.h>
#include <buola/gui/cicon.h>

#include <buola/widgets/clistbox.h>
#include <buola/widgets/ceditbox.h>
#include <buola/widgets/ctheme.h>

#include <buola/app/ctimer.h>

namespace buola { namespace gui {

CEditBox::CEditBox()
    :   mCompleter(nullptr)
{
    SetFocusMode(EFocusMode::MOUSE|EFocusMode::TAB);
    mTimer.reset(new CTimer(gConfig.mKeyboard.GetBlinkTime()));
    mTimer->sTimer.Connect(&CEditBox::OnTimer,this);

    get_default_theme()->PrepareEditBox(this);
    mFont=img::CFont::GetStock(L"control");

    mLayout.SetDefaultFont(mFont);
    mLayout.SetText(mCaption);

    mCursor.Set(0);
    mCursorOn=false;

    mPressed=false;
    mScrollPos={0,0};

    mSelStart=-1;
    mSelEnd=-1;

    SetPointer(CPointer::STOCK_TEXT);
}

CEditBox::~CEditBox()
{}

void CEditBox::SelectAll()
{
    mSelStart=0;
    mSelEnd=mCaption.length();
    UpdateSelection();
    Refresh();
}

void CEditBox::Insert(const std::wstring &pStr)
{
    if(mSelEnd>mSelStart)
        EraseSelection();

    mLayout.InsertText(mCursor,pStr);
    mCaption=mLayout.GetText();
    CalcCursorPos();
    Refresh();
}

void CEditBox::Insert(wchar_t pChar)
{
    if(mSelEnd>mSelStart)
        EraseSelection();

    mLayout.InsertChar(mCursor,pChar);
    mCaption=mLayout.GetText();
    CalcCursorPos();
    Refresh();
}

void CEditBox::EraseSelection()
{
    mCaption.erase(mSelStart,mSelEnd-mSelStart);
    mCursor.Set(mSelStart);
    mSelEnd=-1;
    mSelStart=-1;
    UpdateSelection();
    mLayout.SetText(mCaption);
    CalcCursorPos();
    Refresh();
}

void CEditBox::EraseChar(bool pBack)
{
    mLayout.EraseChar(mCursor,pBack);
    mCaption=mLayout.GetText();
    CalcCursorPos();
    Refresh();
}

void CEditBox::SetFont(img::PFont pFont)
{
    mFont=pFont;
    mLayout.SetDefaultFont(mFont);
    CalcAll();
    Refresh();
}

void CEditBox::SetCaption(const std::wstring &pCaption)
{
    mCaption=pCaption;

    mScrollPos={0,0};
    mCursor.Set(mCaption.length());
    if(mSelStart>=mCursor.AbsIndex()) mSelStart=mCursor.AbsIndex()-1;
    if(mSelEnd>mCursor.AbsIndex()) mSelEnd=mCursor.AbsIndex();
    if(mSelStart<0)
        mSelEnd=-1;
    mLayout.SetText(mCaption);
    CalcCursorPos();
    Refresh();
    UpdateSelection();
}

void CEditBox::CalcAll()
{
    if(GetStyle(STYLE_MULTILINE))
        mLayout.SetWidth(Size().x-6);
    else
        mLayout.UnsetWidth();

    if(GetStyle(STYLE_CENTER))
        mLayout.SetAlignMode(img::CTextLayout::ALIGN_CENTER);
    else
        mLayout.SetAlignMode(img::CTextLayout::ALIGN_LEFT);

    mTextSize=mLayout.GetSize();
    if(GetStyle(STYLE_EDITOR))
        mTextPos.x=3;
    else
        mTextPos.x=5;

    if(GetStyle(STYLE_MULTILINE))
    {
        mTextPos.y=(Size().y-mTextSize.y)/2;
    }
    else
    {
        mTextPos.y=mTextPos.x;
    }

    msg_info() << "mTextPos=" << "\n";
    CalcCursorPos();
}

void CEditBox::CalcCursorPos()
{
    CRect_d lPosInLayout=mLayout.GetCursorPos(mCursor);
    mCursorPos=lPosInLayout+mTextPos-mScrollPos;

    if(mCursorPos.l<0)
    {
        mScrollPos.x+=max(mCursorPos.l-10,-mScrollPos.x);

        mCursorPos=lPosInLayout+mTextPos-mScrollPos;
    }

    if(mCursorPos.l>Size().x)
    {
        mScrollPos.x+=min(mCursorPos.l-Size().x+max(30.0,Size().x*4/10),
                                          min(Size().x,200.0));
        mCursorPos=lPosInLayout+mTextPos-mScrollPos;
    }
}

img::CTextLayout::SCursor CEditBox::CalcCursor(const CPoint_d &pPos)
{
    return mLayout.GetCursor(pPos-mTextPos+mScrollPos);
}

void CEditBox::MoveCursorTo(const img::CTextLayout::SCursor &pCursor)
{
    if(!(mSelEnd>=mSelStart&&mSelStart>=0)&&mCursor==pCursor)
        return; //no selection, same cursor, nothing to do

    mSelStart=-1;
    mSelEnd=-1;
    mCursor=pCursor;
    CalcCursorPos();
    mCursorOn=true;
    mTimer->Enable();
    Refresh();
}

void CEditBox::ExtendSelectionTo(const img::CTextLayout::SCursor &pCursor)
{
    if(pCursor==mCursor) return;

    if(!(mSelEnd>=mSelStart&&mSelStart>=0))
        mDragStart=mCursor.AbsIndex();

    mSelStart=min(mDragStart,pCursor.AbsIndex());
    mSelEnd=max(mDragStart,pCursor.AbsIndex());

    mCursor=pCursor;

    CalcCursorPos();
    Refresh();

    UpdateSelection();
}

void CEditBox::MoveCursor(img::CTextLayout::ECursorMove pMove,bool pSelect)
{
    img::CTextLayout::SCursor lNew=mCursor;

    mLayout.MoveCursor(lNew,pMove);

    if(pSelect)
        ExtendSelectionTo(lNew);
    else
        MoveCursorTo(lNew);
}

void CEditBox::UpdateSelection()
{
    ///\todo implement clipboard in mac and reenable this
//    get_toolkit()->GetClipboard(CClipboard::PRIMARY)->SetString(L"aaa");
}

void CEditBox::GetPropertyNames(std::vector<std::string> &pNames)
{
    pNames.push_back("noborder");
    pNames.push_back("disabled");
    pNames.push_back("readonly");

    CWidget::GetPropertyNames(pNames);
}

void CEditBox::SetProperty(const std::string &p,const UAny &v)
{
    if(ProcessStyleProperty(p,v,"noborder",STYLE_NOBORDER)) return;
    if(ProcessStyleProperty(p,v,"disabled",STYLE_DISABLED)) return;
    if(ProcessStyleProperty(p,v,"readonly",STYLE_READONLY)) return;

    CWidget::SetProperty(p,v);
}

void CEditBox::SetCompleter(SCompleter *pCompleter)
{
    mCompleter=pCompleter;
}

void CEditBox::DoComplete()
{
    std::vector<std::wstring> lList;

    mCompleter->GetCompletionOptions(lList,this,mCaption);

    if(lList.size()==1)
    {
        int lOldLength=mCaption.length();
        SetCaption(lList[0]);
        img::CTextLayout::SCursor lCursor;
        lCursor.Set(lOldLength);
        ExtendSelectionTo(lCursor);
    }
    else if(lList.size()>1)
    {
        ///\todo should have complete list
//         mCompleteList=new CListBox;
//         mCompleteList->SetStyle(CListBox::STYLE_SIMPLEVIEW);
//         mCompleteList->CreatePopup(TranslateCoords(CPoint_d(0,Size().y)),CSize_d(Size().y,200));
//         for(int i=0;i<lList.size();i++)
//         {
//             mCompleteList->AddItem(lList[i],nullptr,i);
//         }
//         mCompleteList->AdjustHeight();
//         mCompleteList->Show();
    }
}

void CEditBox::StopComplete()
{
    if(mCompleteList)
    {
        mCompleteList=nullptr;
    }
}

///////////////////// Event handling //////////////////////

void CEditBox::OnDestroy()
{
    StopComplete();
    mTimer=nullptr;
}

void CEditBox::OnExpose(const CExposeEvent &pE)
{
    get_default_theme()->DrawEditBox(pE.mGC,this,CTheme::DRAW_NONE);
}

void CEditBox::OnButtonDown(const CMouseEvent &pE)
{
    StopComplete();

    if(pE.mButton==EMouseButton::MIDDLE)
    {
        if(GetStyle(STYLE_READONLY)) return;
        std::wstring lString;
        if(get_toolkit()->GetClipboard(CClipboard::PRIMARY)->GetString(lString))
            Insert(lString);
        return;
    }
    else if(pE.mButton==EMouseButton::LEFT)
    {
        img::CTextLayout::SCursor lCursor=CalcCursor(pE.mPoint);

        if(pE.mMods&KEYMOD_SHIFT)
        {
            ExtendSelectionTo(lCursor);
        }
        else
        {
            MoveCursorTo(lCursor);
        }

        mPressed=true;
    }
}

void CEditBox::OnButtonUp(const CMouseEvent &pE)
{
    if(pE.mButton==EMouseButton::LEFT)
    {
        mPressed=false;
    }
}

void CEditBox::OnMouseMove(const CMouseEvent &pE)
{
    if(!mPressed) return;
    img::CTextLayout::SCursor lCursor=CalcCursor(pE.mPoint);

    ExtendSelectionTo(lCursor);
}

void CEditBox::OnFocusIn(const CFocusEvent &pE)
{
    mCursorOn=true;

    mTimer->Enable();

    Refresh();
}

void CEditBox::OnFocusOut(const CFocusEvent &pE)
{
    StopComplete();

    mCursorOn=false;

    Refresh();

    if(mTimer)
        mTimer->Disable();

    eFocusLost();

    ///\todo make sure this is a good solution
    if(GetStyle(STYLE_EDITOR))
    {
        sEndEditor(mCaption);
    }
}

void CEditBox::OnChar(const CKeyEvent &pE)
{
    StopComplete();

    if(pE.mChar<32) return;

    if(GetStyle(STYLE_NUMERIC))
    {
        if(!(iswdigit(pE.mChar)||pE.mChar=='-'))
        {
            if(!GetStyle(STYLE_DOUBLE)||(pE.mChar!='E'&&pE.mChar!='e'&&
                    pE.mChar!='.')) return;
        }
    }
    if(GetStyle(STYLE_READONLY)) return;
    Insert(pE.mChar);

    eChange(true);

    if(mCompleter&&mCursor.AbsIndex()==mCaption.length())
    {
        DoComplete();
    }
}

void CEditBox::OnKeyDown(const CKeyEvent &pE)
{
    if(pE.mPlainChar)
        return;
    
    if(pE.mKey==KEY_LEFT)
    {
        StopComplete();

        MoveCursor(img::CTextLayout::CURSOR_LEFT,pE.mMods&KEYMOD_SHIFT);
    }
    else if(pE.mKey==KEY_RIGHT)
    {
        StopComplete();

        MoveCursor(img::CTextLayout::CURSOR_RIGHT,pE.mMods&KEYMOD_SHIFT);
    }
    else if(pE.mKey==KEY_DOWN)
    {
        if((GetStyle(STYLE_MULTILINE))&&!(pE.mMods&KEYMOD_CONTROL))
        {
            StopComplete();

            MoveCursor(img::CTextLayout::CURSOR_DOWN,pE.mMods&KEYMOD_SHIFT);
        }
        else if(mCompleteList)
        {
            ///\todo complete list
//             TID lID=mCompleteList->GetSelID();
// 
//             if(lID<mCompleteList->GetItemCount()-1)
//             {
//                 mCompleteList->SelectItemByID(lID+1);
//             }
        }
    }
    else if(pE.mKey==KEY_UP)
    {
        if(GetStyle(STYLE_MULTILINE)&&!(pE.mMods&KEYMOD_CONTROL))
        {
            StopComplete();

            MoveCursor(img::CTextLayout::CURSOR_UP,pE.mMods&KEYMOD_SHIFT);
        }
        else if(mCompleteList)
        {
            ///\todo same here
//             TID lID=mCompleteList->GetSelID();
// 
//             if(lID>0)
//             {
//                 mCompleteList->SelectItemByID(lID-1);
//             }
//             else if(lID==0)
//             {
//                 mCompleteList->UnselectAll();
//             }
        }
    }
    else if(pE.mKey==KEY_HOME)
    {
        StopComplete();

        MoveCursor(img::CTextLayout::CURSOR_HOME,pE.mMods&KEYMOD_SHIFT);
    }
    else if(pE.mKey==KEY_END)
    {
        StopComplete();

        MoveCursor(img::CTextLayout::CURSOR_END,pE.mMods&KEYMOD_SHIFT);
    }
    else if(pE.mKey==KEY_DEL)
    {
        StopComplete();

        if(GetStyle(STYLE_READONLY)) return;

        if(mSelEnd>mSelStart)
        {
            EraseSelection();
        }
        else
        {
            EraseChar(false);
        }

        eChange(false);
    }
    else if(pE.mKey==KEY_BACKSPACE)
    {
        StopComplete();

        if(GetStyle(STYLE_READONLY)) return;

        if(mSelEnd>mSelStart)
        {
            EraseSelection();
        }
        else
        {
            EraseChar(true);
        }

        eChange(false);
    }
    else if(pE.mKey==KEY_ESC)
    {
        eEscape();
    }
    else if(pE.mKey==KEY_ENTER)
    {
        if(mCompleteList)
        {
            ///\todo complete list
//             TID lID=mCompleteList->GetSelID();
// 
//             if(lID>=0)
//             {
//                 SetCaption(mCompleteList->GetSelText());
//             }
//             else
//             {
//                 OnEnter();
//             }
// 
//             StopComplete();
        }
        else
        {
            OnEnter();
            if(GetStyle(STYLE_EDITOR))
                sEndEditor(mCaption);
        }
    }
}

void CEditBox::OnSize(const CGeometryEvent &pE)
{
    CalcAll();
}

void CEditBox::OnStyleChanged(TStyle pOld)
{
    int lChange=pOld^GetStyle();

    if(lChange&(STYLE_DISABLED|STYLE_NOBORDER))
    {
        Refresh();
    }
    if(lChange&STYLE_FAKE)
    {
        if(GetStyle(STYLE_FAKE))
        {
            SetInputMode(EInputMode::MOUSETRANSPARENT);
            SetFocusMode(EFocusMode::NONE);
            SetPointer(CPointer::STOCK_DEFAULT);
        }
        else
        {
            SetInputMode(EInputMode::NORMAL);
            SetFocusMode(EFocusMode::TAB|EFocusMode::MOUSE);
            SetPointer(CPointer::STOCK_TEXT);
        }
    }
    if(lChange&STYLE_SMALLFONT)
    {
        if(GetStyle(STYLE_SMALLFONT))
        {
            SetFont(img::CFont::GetStock(L"small"));
        }
        else
        {
            SetFont(img::CFont::GetStock(L"control"));
        }
    }
    if(lChange&STYLE_NUMERIC)
    {
        if(GetStyle(STYLE_NUMERIC))
        {
            if(!mCaption.empty())
            {
                if(iswdigit(mCaption[0]))
                    SetCaption(to_wstring(to_<int>(mCaption)));
                else
                    SetCaption(L"");
            }
        }
    }
    if(lChange&(STYLE_MULTILINE|STYLE_CENTER))
    {
        CalcAll();
    }
}

void CEditBox::OnTimer()
{
    mCursorOn=!mCursorOn;
    ///\todo use painting version
/*    if(mCursorOn)
    {
        img::CGC *lGC=StartPainting();
        get_default_theme()->DrawCursor(lGC,mCursorPos);
        EndPainting(lGC);
    }
    else*/
    {
        Refresh();
    }
}

void CEditBox::OnEnter()
{
    eEnter(mCaption);
}

/*namespace gui*/ } /*namespace buola*/ }
